// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'dart:ui' as dart_ui;

import 'package:charts_grasp/src/chart/utils/enum.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:charts_grasp/src/circular_chart/base/circular_area.dart';
import 'package:charts_grasp/src/circular_chart/base/circular_state_properties.dart';
import 'package:charts_grasp/src/circular_chart/base/series_base.dart';
import 'package:charts_grasp/src/circular_chart/renderer/chart_point.dart';
import 'package:charts_grasp/src/circular_chart/renderer/circular_chart_annotation.dart';
import 'package:charts_grasp/src/circular_chart/renderer/circular_series.dart';
import 'package:charts_grasp/src/circular_chart/renderer/circular_series_controller.dart';
import 'package:charts_grasp/src/circular_chart/renderer/common.dart';
import 'package:charts_grasp/src/circular_chart/renderer/renderer_base.dart';
import 'package:charts_grasp/src/circular_chart/renderer/renderer_extension.dart';
import 'package:charts_grasp/src/circular_chart/utils/helper.dart';
import 'package:charts_grasp/src/common/common.dart';
import 'package:charts_grasp/src/common/legend/legend.dart';
import 'package:charts_grasp/src/common/legend/renderer.dart';
import 'package:charts_grasp/src/common/rendering_details.dart';
import 'package:charts_grasp/src/common/template/rendering.dart';
import 'package:charts_grasp/src/common/user_interaction/tooltip.dart';
import 'package:charts_grasp/src/common/user_interaction/tooltip_rendering_details.dart';
import 'package:charts_grasp/src/common/utils/enum.dart';
import 'package:charts_grasp/src/common/utils/helper.dart';
import 'package:charts_grasp/src/common/utils/typedef.dart';
import 'package:charts_grasp/src/core/theme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';

class CgCircularChart extends StatefulWidget {
  CgCircularChart({
    super.key,
    this.backgroundColor,
    this.backgroundImage,
    this.annotations,
    this.borderColor = Colors.transparent,
    this.borderWidth = 0.0,
    this.onLegendItemRender,
    this.onTooltipRender,
    this.onDataLabelRender,
    this.onDataLabelTapped,
    this.onLegendTapped,
    this.onSelectionChanged,
    this.onChartTouchInteractionUp,
    this.onChartTouchInteractionDown,
    this.onChartTouchInteractionMove,
    this.onCreateShader,
    this.palette = const <Color>[
      Color.fromRGBO(75, 135, 185, 1),
      Color.fromRGBO(192, 108, 132, 1),
      Color.fromRGBO(246, 114, 128, 1),
      Color.fromRGBO(248, 177, 149, 1),
      Color.fromRGBO(116, 180, 155, 1),
      Color.fromRGBO(0, 168, 181, 1),
      Color.fromRGBO(73, 76, 162, 1),
      Color.fromRGBO(255, 205, 96, 1),
      Color.fromRGBO(255, 240, 219, 1),
      Color.fromRGBO(238, 238, 238, 1),
    ],
    ChartTitle? title,
    EdgeInsets? margin,
    List<CircularSeries<dynamic, dynamic>>? series,
    Legend? legend,
    String? centerX,
    String? centerY,
    TooltipBehavior? tooltipBehavior,
    ActivationMode? selectionGesture,
    bool? enableMultiSelection,
  })  : series = series = series ?? <CircularSeries<dynamic, dynamic>>[],
        title = title ?? ChartTitle(),
        legend = legend ?? const Legend(),
        margin = margin ?? const EdgeInsets.fromLTRB(10, 10, 10, 10),
        centerX = centerX ?? '50%',
        centerY = centerY ?? '50%',
        tooltipBehavior = tooltipBehavior ?? TooltipBehavior(),
        selectionGesture = selectionGesture ?? ActivationMode.singleTap,
        enableMultiSelection = enableMultiSelection ?? false;

  final ChartTitle title;

  final List<CircularSeries<dynamic, dynamic>> series;

  final EdgeInsets margin;

  final Legend legend;

  final TooltipBehavior tooltipBehavior;

  final Color? backgroundColor;

  final List<CircularChartAnnotation>? annotations;

  final Color borderColor;

  final double borderWidth;

  final ImageProvider? backgroundImage;

  final String centerX;

  final String centerY;

  final CircularLegendRenderCallback? onLegendItemRender;

  final CircularTooltipCallback? onTooltipRender;

  final CircularDatalabelRenderCallback? onDataLabelRender;

  final CircularShaderCallback? onCreateShader;

  final DataLabelTapCallback? onDataLabelTapped;

  final ChartLegendTapCallback? onLegendTapped;

  final CircularSelectionCallback? onSelectionChanged;

  final CircularTouchInteractionCallback? onChartTouchInteractionUp;

  final CircularTouchInteractionCallback? onChartTouchInteractionDown;

  final CircularTouchInteractionCallback? onChartTouchInteractionMove;

  final List<Color> palette;

  final ActivationMode selectionGesture;

  final bool enableMultiSelection;

  @override
  State<StatefulWidget> createState() => CgCircularChartState();
}

class CgCircularChartState extends State<CgCircularChart> with TickerProviderStateMixin {
  late CircularStateProperties _stateProperties;

  @override
  void initState() {
    _stateProperties = CircularStateProperties(renderingDetails: RenderingDetails(), chartState: this);

    _stateProperties.isToggled = false;
    _initializeDefaultValues();
    _createAndUpdateSeriesRenderer();
    super.initState();
  }

  @override
  void didChangeDependencies() {
    _stateProperties.renderingDetails.chartTheme =
        _updateThemeData(context, Theme.of(context), CgChartTheme.of(context));
    _stateProperties.renderingDetails.themeData = Theme.of(context);
    _stateProperties.isRtl = Directionality.of(context) == TextDirection.rtl;
    super.didChangeDependencies();
  }

  CgChartThemeData _updateThemeData(BuildContext context, ThemeData themeData, CgChartThemeData chartThemeData) {
    chartThemeData = chartThemeData.copyWith(
      titleTextStyle: themeData.textTheme.bodySmall!
          .copyWith(color: chartThemeData.titleTextColor, fontSize: 15)
          .merge(chartThemeData.titleTextStyle)
          .merge(widget.title.textStyle),
      tooltipTextStyle: themeData.textTheme.bodySmall!
          .copyWith(color: widget.tooltipBehavior.color ?? chartThemeData.tooltipLabelColor)
          .merge(chartThemeData.tooltipTextStyle)
          .merge(widget.tooltipBehavior.textStyle),
      legendTitleTextStyle: themeData.textTheme.bodySmall!
          .copyWith(color: chartThemeData.legendTitleColor)
          .merge(chartThemeData.legendTitleTextStyle)
          .merge(widget.legend.title.textStyle),
      legendTextStyle: themeData.textTheme.bodySmall!
          .copyWith(color: chartThemeData.legendTextColor, fontSize: 13)
          .merge(chartThemeData.legendTextStyle)
          .merge(widget.legend.textStyle),
    );
    return chartThemeData;
  }

  @override
  void didUpdateWidget(CgCircularChart oldWidget) {
    _createAndUpdateSeriesRenderer(oldWidget);

    needsRepaintCircularChart(_stateProperties.chartSeries.visibleSeriesRenderers,
        <CircularSeriesRendererExtension?>[_stateProperties.prevSeriesRenderer],);

    _stateProperties.needExplodeAll = widget.series.isNotEmpty &&
        (widget.series[0].explodeAll &&
            widget.series[0].explode &&
            oldWidget.series[0].explodeAll != widget.series[0].explodeAll);
    _stateProperties.renderingDetails.isLegendToggled = false;
    _stateProperties.renderingDetails.widgetNeedUpdate = true;
    if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) {
      _stateProperties.renderingDetails.legendWidgetContext.clear();
    }

    final TooltipRenderingDetails tooltipRenderingDetails =
        TooltipHelper.getRenderingDetails(_stateProperties.renderingDetails.tooltipBehaviorRenderer);
    if (tooltipRenderingDetails.chartTooltipState != null) {
      tooltipRenderingDetails.show = false;
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    _stateProperties.renderingDetails.initialRender =
        (_stateProperties.renderingDetails.widgetNeedUpdate && !_stateProperties.renderingDetails.isLegendToggled)
            ? _stateProperties.needExplodeAll
            : (_stateProperties.renderingDetails.initialRender == null);
    _stateProperties.renderingDetails.oldDeviceOrientation =
        _stateProperties.renderingDetails.oldDeviceOrientation == null
            ? MediaQuery.of(context).orientation
            : _stateProperties.renderingDetails.deviceOrientation;
    _stateProperties.renderingDetails.deviceOrientation = MediaQuery.of(context).orientation;
    _stateProperties.isTooltipOrientationChanged = false;

    final Widget container = ChartContainer(
      child: GestureDetector(
          child: DecoratedBox(
        decoration: BoxDecoration(
            color: widget.backgroundColor ?? _stateProperties.renderingDetails.chartTheme.plotAreaBackgroundColor,
            image: widget.backgroundImage != null
                ? DecorationImage(image: widget.backgroundImage!, fit: BoxFit.fill)
                : null,
            border: Border.all(color: widget.borderColor, width: widget.borderWidth),),
        child: Column(
          children: <Widget>[renderChartTitle(_stateProperties), _renderChartElements()],
        ),
      ),),
    );
    return RepaintBoundary(child: container);
  }

  @override
  void dispose() {
    disposeAnimationController(_stateProperties.renderingDetails.animationController, _repaintChartElements);
    super.dispose();
  }

  Future<dart_ui.Image> toImage({double pixelRatio = 1.0}) async {
    final RenderRepaintBoundary boundary = context.findRenderObject() as RenderRepaintBoundary;
    final dart_ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
    return image;
  }

  void _initializeDefaultValues() {
    _stateProperties.chartSeries = CircularSeriesBase(_stateProperties);
    _stateProperties.circularArea = CircularArea(stateProperties: _stateProperties);
    _stateProperties.renderingDetails.chartLegend = ChartLegend(_stateProperties);
    _stateProperties.needToMoveFromCenter = true;
    _stateProperties.renderingDetails.animateCompleted = false;
    _stateProperties.renderingDetails.annotationController = AnimationController(vsync: this);
    _stateProperties.renderingDetails.seriesRepaintNotifier = ValueNotifier<int>(0);
    _stateProperties.renderingDetails.legendWidgetContext = <MeasureWidgetContext>[];
    _stateProperties.renderingDetails.explodedPoints = <int>[];
    _stateProperties.renderingDetails.templates = <ChartTemplateInfo>[];
    _stateProperties.renderingDetails.legendToggleStates = <LegendRenderContext>[];
    _stateProperties.renderingDetails.legendToggleTemplateStates = <MeasureWidgetContext>[];
    _stateProperties.renderingDetails.dataLabelTemplateRegions = <Rect>[];
    _stateProperties.annotationRegions = <Rect>[];
    _stateProperties.renderingDetails.widgetNeedUpdate = false;
    _stateProperties.renderingDetails.isLegendToggled = false;
    _stateProperties.renderingDetails.selectionData = <int>[];
    _stateProperties.renderingDetails.animationController = AnimationController(vsync: this)
      ..addListener(_repaintChartElements);
    _stateProperties.renderingDetails.tooltipBehaviorRenderer = TooltipBehaviorRenderer(_stateProperties);
    _stateProperties.renderingDetails.legendRenderer = LegendRenderer(widget.legend);
  }

  void _createAndUpdateSeriesRenderer([CgCircularChart? oldWidget]) {
    if (widget.series.isNotEmpty) {
      final CircularSeriesRendererExtension? oldSeriesRenderer = oldWidget != null && oldWidget.series.isNotEmpty
          ? _stateProperties.chartSeries.visibleSeriesRenderers[0]
          : null;
      dynamic series;
      series = widget.series[0];

      CircularSeriesRendererExtension? seriesRenderer;

      if (_stateProperties.prevSeriesRenderer != null &&
          !_stateProperties.prevSeriesRenderer!.stateProperties.isToggled &&
          isSameSeries(_stateProperties.prevSeriesRenderer!.series, series)) {
        // ignore: unnecessary_null_checks
        seriesRenderer = _stateProperties.prevSeriesRenderer!;
      } else {
        final CircularSeriesRenderer renderer = series.createRenderer(series);
        if (renderer is CircularSeriesRendererExtension) {
          seriesRenderer = renderer;
        } else {
          if (renderer is PieSeriesRenderer) {
            seriesRenderer = PieSeriesRendererExtension();
          } else if (renderer is DoughnutSeriesRenderer) {
            seriesRenderer = DoughnutSeriesRendererExtension();
          } else if (renderer is RadialBarSeriesRenderer) {
            seriesRenderer = RadialBarSeriesRendererExtension();
          }
        }
        seriesRenderer!.renderer = ChartSeriesRender();
        if (seriesRenderer.controller == null && series.onRendererCreated != null) {
          seriesRenderer.controller = CircularSeriesController(seriesRenderer);
          series.onRendererCreated!(seriesRenderer.controller);
        }
      }
      if (oldWidget != null && oldWidget.series.isNotEmpty) {
        _stateProperties.prevSeriesRenderer = oldSeriesRenderer;
        _stateProperties.prevSeriesRenderer!.series = oldWidget.series[0];
        _stateProperties.prevSeriesRenderer!.oldRenderPoints = <ChartPoint<dynamic>>[
          ...?_stateProperties.prevSeriesRenderer!.renderPoints,
        ];
        _stateProperties.prevSeriesRenderer!.renderPoints = <ChartPoint<dynamic>>[];
      }
      seriesRenderer.series = series;
      seriesRenderer.isSelectionEnable = series.selectionBehavior.enable == true;
      seriesRenderer.stateProperties = _stateProperties;
      _stateProperties.chartSeries.visibleSeriesRenderers
        ..clear()
        ..add(seriesRenderer);
    }
  }

  void _repaintChartElements() {
    _stateProperties.renderingDetails.seriesRepaintNotifier.value++;
  }

  void _refresh() {
    final List<MeasureWidgetContext> legendContexts = _stateProperties.renderingDetails.legendWidgetContext;
    if (legendContexts.isNotEmpty) {
      MeasureWidgetContext templateContext;
      RenderBox renderBox;
      for (int i = 0; i < legendContexts.length; i++) {
        templateContext = legendContexts[i];
        final context = templateContext.context;
        if (context != null && context.mounted) {
          renderBox = context.findRenderObject() as RenderBox;
          templateContext.size = renderBox.size;
        }
      }
      _stateProperties.legendRefresh = true;
      if (mounted) {
        setState(() {});
      }
    }
  }

  Widget _renderChartElements() {
    return Expanded(
      child: LayoutBuilder(builder: (context, constraints) {
        Widget element;
        _initialize(constraints);
        _stateProperties.renderingDetails.prevSize = _stateProperties.renderingDetails.prevSize ?? constraints.biggest;
        _stateProperties.renderingDetails.didSizeChange =
            _stateProperties.renderingDetails.prevSize != constraints.biggest;
        _stateProperties.renderingDetails.prevSize = constraints.biggest;
        final ChartPoint<dynamic> tooltipPoint = _getChartPoints(_stateProperties);
        SchedulerBinding.instance.addPostFrameCallback((_) {
          _validateStateMaintenance(_stateProperties, tooltipPoint);
        });
        _stateProperties.chartSeries.findVisibleSeries();
        if (_stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty) {
          _stateProperties.chartSeries.processDataPoints(_stateProperties.chartSeries.visibleSeriesRenderers[0]);
        }
        final List<Widget> legendTemplates = bindLegendTemplateWidgets(_stateProperties);
        if (legendTemplates.isNotEmpty && _stateProperties.renderingDetails.legendWidgetContext.isEmpty) {
          element = Stack(children: legendTemplates);
          SchedulerBinding.instance.addPostFrameCallback((_) => _refresh());
        } else {
          _stateProperties.renderingDetails.chartLegend
              .calculateLegendBounds(_stateProperties.renderingDetails.chartLegend.chartSize);
          element = getElements(_stateProperties, CircularArea(stateProperties: _stateProperties), constraints)!;
        }
        return element;
      },),
    );
  }

  void _initialize(BoxConstraints constraints) {
    final num width = constraints.maxWidth;
    final num height = constraints.maxHeight;
    final EdgeInsets margin = widget.margin;
    final bool isMobilePlatform =
        defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS;
    _stateProperties.renderingDetails.legendRenderer.legendPosition = (widget.legend.position == LegendPosition.auto)
        ? (height > width
            ? isMobilePlatform
                ? LegendPosition.top
                : LegendPosition.bottom
            : LegendPosition.right)
        : widget.legend.position;
    _stateProperties.renderingDetails.chartLegend.chartSize =
        Size(width - margin.left - margin.right, height - margin.top - margin.bottom);
  }

  ChartPoint<dynamic> _getChartPoints(CircularStateProperties stateProperties) {
    final TooltipBehaviorRenderer tooltipBehaviorRenderer = stateProperties.renderingDetails.tooltipBehaviorRenderer;
    final TooltipRenderingDetails tooltipRenderingDetails = TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer);

    ChartPoint<dynamic> tooltipChartPoint = ChartPoint<dynamic>();

    if (stateProperties.renderingDetails.oldDeviceOrientation != stateProperties.renderingDetails.deviceOrientation ||
        stateProperties.renderingDetails.didSizeChange) {
      if (tooltipRenderingDetails.showLocation != null &&
          stateProperties.chart.tooltipBehavior.enable == true &&
          stateProperties.isTooltipHidden == false &&
          stateProperties.requireDataLabelTooltip == null) {
        tooltipChartPoint = circularPixelToPoint(tooltipRenderingDetails.showLocation!, stateProperties);
      }
    }
    return tooltipChartPoint;
  }

  void _validateStateMaintenance(CircularStateProperties stateProperties, ChartPoint<dynamic> tooltipChartPoint) {
    final TooltipBehaviorRenderer tooltipBehaviorRenderer = stateProperties.renderingDetails.tooltipBehaviorRenderer;
    final TooltipRenderingDetails tooltipRenderingDetails = TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer);
    if (stateProperties.renderingDetails.oldDeviceOrientation != stateProperties.renderingDetails.deviceOrientation ||
        stateProperties.renderingDetails.didSizeChange) {
      if (tooltipRenderingDetails.showLocation != null &&
          stateProperties.chart.tooltipBehavior.enable &&
          !stateProperties.isTooltipHidden &&
          stateProperties.requireDataLabelTooltip == null) {
        stateProperties.isTooltipOrientationChanged = true;
        ChartPoint<dynamic>? point;
        for (int i = 0; i < stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints.length; i++) {
          if (stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == tooltipChartPoint.x &&
              stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == tooltipChartPoint.y) {
            point = stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints[i];
          }
        }
        if (point != null) {
          final Offset tooltipPosition = circularPointToPixel(point, stateProperties);
          if (stateProperties.chart.tooltipBehavior.builder != null) {
            stateProperties.circularArea.showCircularTooltipTemplate(0, point.index);
          } else {
            tooltipRenderingDetails.internalShowByPixel(tooltipPosition.dx, tooltipPosition.dy);
          }
        }
      }
    }
  }
}
